<script>
  import $ from '../dfish.js'
  import {
    h,
    mergeProps
  } from 'vue'
  import WidgetBase from './WidgetBase.vue'
  import AlignParent from './AlignParent.vue'
  import Label from './Label.vue'

  const _validate_dft_value = {
    required: true,
    beforeNow: true,
    afterNow: true
  }


  export default {
    mixins: [WidgetBase, AlignParent],
    props: ['name', 'value', 'modelValue', 'placeholder', 'label', 'readonly', 'disabled', 'required',
      'validateDefault', 'validateHandler', 'inputWidth', 'inputHeight'
    ],
    emits: ['update:modelValue', 'change', 'blur', 'focus'],
    data() {
      let mv = this.wrapper().modelValue
      return {
        isFormWidget: true,
        isFocus: false,
        hasBorder: false,
        isPlural: false,
        warnClass: '',
        labelVertical: false,
        modifiedValue: mv,
        defaultValue: mv,
        placeholder_type: '',
      }
    },
    methods: {
      val(v) {
        if (arguments.length == 0)
          return this.x.modelValue
        let ov = this.x.modelValue
        this.x.modelValue = v
        let changed = ov !== v
        if (typeof ov === 'object') {
          changed = JSON.stringify(ov) != JSON.stringify(v)
        }
        if (changed) {
          this.warnClass && this.warn(false)
          this.trigger('update:modelValue', v)
          this.trigger('change', this, v)
        }
        return v
      },
      formVal() {
        return this.input_value
      },
      // @empty -> 是否强制清空
      reset(empty) {
        this.val(empty || this.defaultValue == null ? '' : this.defaultValue)
      },
      defaultRuleValue(name) {
        let r = this.rules[name]
        if (r != null) {
          if (Array.isArray(r)) {
            r = r[0]
          }
          return r && r.value ? r.value : r
        }
      },
      valid(trigger) {
        let e = this.getValidError(trigger)
        if (e) {
          this.warn()
          this.showErrorTip(e.text)
        }
        return !e
      },
      getValidError(trigger) {
        let ques = trigger ? this.validTriggers[trigger] : this.validQueues
        if (ques && (this.isNormal || this.readonly == 'validate')) {
          let v = this.isPlural ? this.groupFormVal() : this.formVal()
          if (typeof v !== 'string')
            v = Array.isArray(v) ? v.join(',') : v == null ? '' : ('' + v);
          v = v.trim()
          let hs = this.validHooks
          for (let q of ques) {
            let rv = typeof q.rule !== 'object' ? q.rule : q.rule.value !== undefined ? q.rule.value : q.rule
            let e = hs[q.type](v, rv, q)
            if (e) return e
          }
        }
      },
      formErr(que, arg) {
        let label = this.getLabelText()
        let type = que.type
        let text = que.rule && que.rule.text
        if (!text) {
          text = $.loc.ps.apply(null, [$.loc.form[type], label || ''].concat(arg != null && arg !== '' ? arg : [])) || ''
        }
        return {
          wid: this.uid,
          name: this.x.name,
          type,
          label,
          text,
        }
      },
      warn(a) {
        let w = typeof a === 'string' ? a : a !== false ? 'z-err' : ''
        this.warnClass = w
        if (!w && this._tip) {
          $.close(this._tip)
          this._tip = null
        }
      },
      showErrorTip(text) {
        $.scrollIntoView(this)
        this._tip = this.exec({
          type: 'Tip',
          text,
          face: 'warn',
          independent: true,
          snap: {
            target: this.errorTipTarget
          }
        })
      },
      showTip(o, opt) {
        return this.exec($.extendDeep({}, o, this.x.tip, {
          type: 'Tip',
          hoverDrop: true
        }))
      },
      focus() {
        this.$refs.v.focus()
        return this
      },
      formText() {
        let t = this.val()
        return t == null ? '' : t
      },
      getLabelText() {
        let t = this.x.label || (this.pubParent && this.pubParent.x.label)
        return t ? (t.text || t) : ''
      },
      oninput(e) {
        this.val(e.target.value)
      },
      onfocus(e) {
        this.isFocus = true
        this.warn(false)
        this.trigger(e)
      },
      onblur(e) {
        if (!this.isFocusTarget(e.relatedTarget)) this.isFocus = false
        this.trigger(e)
        setTimeout(() => {
          if (!this.$.isUnmounted && !this.$.isDeactivated) this.valid('blur')
        }, 100)
      },
      snapElem() {
        return this.el('main')
      },
      hasBubble(a) {
        return this.snapElem().contains(a.isWidget ? a.el() : a)
      },
      isFocusTarget(el) {
        // implement
      },
      // @original: 检测表单是否有修改，对照参考的值为初始值
      isModified(original) {
        let dv = original ? this.defaultValue : this.modifiedValue
        let mv = this.x.modelValue
        if (dv == null) dv = ''
        if (mv == null) mv = ''
        if (dv == mv)
          return false
        return JSON.stringify(dv) != JSON.stringify(mv)
      },
      // @original: 设置为true，修改初始值
      saveModified(original) {
        this.modifiedValue = $.lodash.clone(this.x.modelValue)
        if (original) {
          this.defaultValue = this.modifiedValue
        }
      },
      html_label() {
        return this.$slots.label ? this.$slots.label() : (this.x.label && typeof this.x.label === 'object' && h(Label,
          this.x.label))
      },
      html_input() {
        return h(this.input_tag, {
          class: 'w-input-value',
          ref: 'v',
          id: this.uid + 'v',
          type: this.input_type,
          name: this.input_name,
          value: this.input_value,
          readonly: this.isReadonly,
          disabled: this.isDisabled,
          placeholder: this.input_placeholder,
          autocomplete: 'off', //off, nope, new-password
          //list: 'autocompleteOff',
          //role: 'presentation',
          oninput: this.oninput,
          onfocus: this.onfocus,
          onblur: this.onblur,
          ...this.input_prop,
        })
      },
      html_input_hidden() {
        return this.x.name ? h('input', {
          type: 'hidden',
          ref: 'v',
          name: this.input_name,
          value: this.input_value,
          disabled: this.x.disabled,
        }) : []
      },
      html_button() {
        return []
      },
      html_form() {
        return [
          this.slot('prepend'),
          this.html_input(),
          this.html_button(),
          this.slot('append'),
        ]
      },
      html_main() {
        let fh = this.x.inputHeight && $.unitSize(this.x.inputHeight)
        return h('div', mergeProps({
          ref: 'main',
          id: this.uid + 'main',
          class: ['w-input', {
            'w-input-border': this.hasBorder,
            [`f-${this.dir}-gap`]: $.br.compat.gap,
          }],
          style: {
            'height': fh
          },
          title: this.input_title
        }, this.main_prop), this.html_form())
      },
      html_slots() {
        return [this.html_label(), this.html_main(), this.slot('after'), this.slot('dialog')]
      }
    },
    computed: {
      rules() {
        return this.x
      },
      pubValidHooks() {
        return {
          required: (v, rule, que) => {
            if (rule && !v) return this.formErr(que)
          },
          pattern: (v, rule, que) => {
            if (v && (typeof rule === 'string' ? !$.eval(`${rule}.test(v)`, {rule, v}) : !rule.test(v)))
              return this.formErr(que)
          },
          validateHandler: (v, rule) => {
            let text = rule && rule(v)
            if (text) {
              return this.formErr({type: 'validateHandler', rule: {text}})
            }
          }
        }
      },
      validHooks() {
        return this.pubValidHooks
      },
      validQueues() {
        let r = []
        let type = 'validateDefault'
        if (this.validHooks[type]) r.push({type})
        for(let k in this.validHooks) {
          if (!r.includes(k)) {
            let rule = this.rules[k]
            if (rule != null) {
              if (Array.isArray(rule)) {
                rule.forEach(item => r.push({type: k, rule: item}))
              } else {
                r.push({type: k, rule})
              }
            }
          }
        }
        return r
      },
      validTriggers() {
        let r = {}
        Object.keys(this.validHooks).forEach(k => {
          let d = this.rules[k]
          if (d) {
            if (Array.isArray(d)) {
              for (let e of d) {
                if (e.trigger) {
                  (r[e.trigger] || (r[e.trigger] = [])).push({type: k, rule: e})
                }
              }
            } else if (d.trigger) {
              (r[d.trigger] || (r[d.trigger] = [])).push({type: k, rule: d})
            }
          }
        })
        return r
      },
      input_value() {
        return this.x.modelValue
      },
      input_name() {
        return this.x.name
      },
      input_tag() {
        return 'input'
      },
      input_type() {
        return 'text'
      },
      input_title() {
        // implement
      },
      input_prop() {
        // implement
      },
      input_placeholder() {
        let s = this.x.placeholder
        if (s == null && $.x.autoPlaceholder && this.x.label) {
          s = $.loc.ps($.loc[this.placeholder_type || 'placeholder_input'], (this.x.label.text || this.x.label))
        }
        return s
      },
      main_prop() {
        // implement
      },
      isNormal() {
        return !this.isReadonly && !this.isDisabled
      },
      isDisabled() {
        return this.x.disabled
      },
      isReadonly() {
        return this.x.readonly
      },
      errorTipTarget() {
        return this
      },
      html_on() {
        // implement
      },
      html_props() {
        let fw = this.x.inputWidth && $.unitSize(this.x.inputWidth)
        return {
          class: ['w-formbase', this.warnClass, this.html_align_class, {
            'z-on': this.isFocus,
            'z-ds': !this.isNormal,
            'z-req': this.rules.required,
            'z-label-vertical': this.labelVertical,
            'z-fixed': !!fw,
          }],
          style: {
            '--w-input-width': fw == null ? '' : fw
          },
          ...this.html_on
        }
      }
    }
  }
</script>

<style>
  .w-label {
    display: flex;
    align-items: center;
    padding-right: 10px;
    flex-shrink: 0;
    justify-content: flex-end;
    box-sizing: border-box;
  }

  .w-label.z-vertical {
    justify-content: flex-start;
  }

  .w-label-text {
    text-align: right;
  }

  .w-formbase {
    display: flex;
    box-sizing: border-box;
    align-items: center;
  }

  .w-input {
    display: flex;
    box-sizing: border-box;
    min-height: 32px;
    overflow: hidden;
    align-items: center;
    flex: 1;
    flex-shrink: 1;
    flex-basis: var(--w-input-width);
  }

  .w-formbase.z-label-vertical {
    display: block;
  }

  .w-formbase.z-label-vertical>.w-input {
    max-width: var(--w-input-width);
  }

  .w-formbase.z-fixed>.w-input {
    flex-grow: 0;
  }

  .w-input-border {
    border: 1px solid #ddd;
    box-sizing: border-box;
    background: #fff;
  }

  .w-input-bbr {
    display: flex;
    flex-shrink: 0;
    width: 26px;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    cursor: pointer;
  }

  .w-input-bbr.f-i-caret-down {
    font-size: 18px;
  }

  .w-input-bbr button {
    width: 100%;
  }

  .w-input-main button:hover {
    color: var(--w--focus-color);
  }

  .w-input-bbr,
  .w-input-bbr button,
  .w-input-main button:disabled {
    color: #a3a3a3;
  }

  .w-formbase.z-on>.w-input-border,
  .w-input-border.z-drop {
    border-color: var(--w--focus-color);
  }

  .w-input-value {
    box-sizing: border-box;
  }

  .w-input-border>.w-input-value {
    width: 100%;
    border: 0;
    padding: 1px 0 1px 10px;
    background: transparent;
  }

  input::placeholder,
  textarea::placeholder,
  .w-input-placeholder {
    color: #a6a6a6;
  }

  .w-dropbox-text {
    box-sizing: border-box;
    width: 100%;
    padding-left: 10px;
  }

  .w-textarea .w-input-border>.w-input-value {
    height: 100%;
    padding: 5px 10px;
    box-sizing: border-box;
    font-family: inherit;
  }

  .w-textarea .w-input-border {
    height: 100%;
  }

  .w-checkbox,
  .w-triplebox,
  .w-radio {
    margin: 0
  }

  .w-triplebox {
    display: inline-flex;
    justify-content: center;
  }

  .w-checkboxgroup>.w-input,
  .w-radiogroup>.w-input {
    flex-wrap: wrap;
    padding-top: 3px;
    padding-bottom: 3px;
    gap: 10px;
    --w-compat-gap: 10px;
  }
  
  .w-checkboxgroup.z-vertical>.w-input,
  .w-radiogroup.z-vertical>.w-input {
    flex-direction: column;
    align-items: flex-start;
  }

  .w-boxgroup-grid {
    flex: 1;
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 10px;
  }

  .w-checkbox-target>*,
  .w-radio-target>* {
    width: 100%;
  }

  .w-checkboxgroup>.w-input>.w-checkbox>.w-input,
  .w-radiogroup>.w-input>.w-radio>.w-input {
    min-height: auto;
    box-sizing: border-box;
  }

  .w-checkbox-text,
  .w-radio-text,
  .w-triplebox-text {
    padding-left: 5px;
  }

  .w-checkbox-appear,
  .w-radio-appear,
  .w-triplebox-appear {
    position: relative;
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: 1px solid #ccc;
    box-sizing: border-box;
    line-height: 0;
    color: var(--w--focus-color);
    background-color: #fff;
    box-shadow: 0 1px 2px rgb(0 0 0 / 5%), inset 0px -15px 10px -12px rgb(0 0 0 / 5%);
  }

  .w-radio-appear {
    border-radius: 20px;
  }

  .w-checkbox .w-input-value,
  .w-radio .w-input-value,
  .w-triplebox .w-input-value {
    display: none;
  }

  .w-checkbox-appear .f-i,
  .w-radio-appear .f-i,
  .w-triplebox-appear .f-i {
    visibility: hidden;
    font-size: 14px;
  }

  .w-radio-appear .f-i {
    width: 10px;
    height: 10px;
    background: var(--w--focus-color);
    border-radius: 20px;
  }

  .w-radio-text,
  .w-checkbox-text,
  .w-triplebox-text {
    word-break: keep-all;
  }

  .w-input-value:checked+.w-checkbox-appear,
  .w-input-value:checked+.w-triplebox-appear,
  .w-input-value:checked+.w-radio-appear {
    border-color: var(--w--focus-color);
    box-shadow: none;
  }

  .w-input-value:checked+.w-checkbox-appear .f-i,
  .w-input-value:checked+.w-triplebox-appear .f-i,
  .w-input-value:checked+.w-radio-appear .f-i {
    visibility: visible;
  }

  .w-triplebox.z-half .w-triplebox-appear:before {
    content: '';
    position: absolute;
    background: var(--w--focus-color);
    flex-shrink: 0;
    width: 10px;
    height: 3px;
  }

  .w-formbase.z-ds .w-input-border,
  .w-formbase.z-ds .w-checkbox-appear,
  .w-formbase.z-ds .w-triplebox-appear,
  .w-formbase.z-ds .w-radio-appear {
    border-color: #ccc;
    background-color: #e6e6e6;
  }

  .w-formbase.z-err .w-input-border,
  .w-formbase.z-err .w-radio-appear,
  .w-formbase.z-err .w-triplebox-appear,
  .w-formbase.z-err .w-checkbox-appear,
  .w-form.z-err > .w-input{
    border: 1px solid #f00;
  }

  .w-formbase.z-req>.w-label>.w-label-text {
    position: relative;
  }

  .w-formbase.z-req>.w-label>.w-label-text>span:before {
    content: '*';
    color: #f00;
    font-size: 16px;
    font-family: tahoma;
    position: relative;
    top: 3px;
    margin-right: 3px;
  }

  .w-label.z-h-align-left {
    padding-left: 5px;
  }

  .w-label.z-h-align-left>.w-label-text {
    text-align: left;
  }

  .w-formbase.z-req>.w-label.z-h-align-left>.w-label-text>span:before {
    content: '';
    display: none;
  }

  .w-formbase.z-req>.w-label.z-h-align-left>.w-label-text>span:after {
    content: '*';
    color: #f00;
    font-size: 16px;
    font-family: tahoma;
    position: relative;
    top: 3px;
    margin-left: 3px;
  }

  .w-form>.w-input {
    word-break: break-word;
    gap: var(--w-form-gap);
  }

  .w-form.z-vertical>.w-input {
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
  }

  .w-form.z-v-align-left>.w-input,
  .w-form.z-h-valign-top>.w-input {
    align-items: flex-start;
  }

  .w-form.z-v-align-center>.w-input,
  .w-form.z-h-valign-middle>.w-input {
    align-items: center;
  }

  .w-form.z-v-align-right>.w-input,
  .w-form.z-h-valign-bottom>.w-input {
    align-items: flex-end;
  }

  .w-form.z-h-align-left>.w-input,
  .w-form.z-v-valign-top>.w-input {
    justify-content: flex-start;
  }

  .w-form.z-h-align-center>.w-input,
  .w-form.z-v-valign-middle>.w-input {
    justify-content: center;
  }

  .w-form.z-h-align-right>.w-input,
  .w-form.z-v-valign-bottom>.w-input {
    justify-content: flex-end;
  }
  
  .w-text > .w-input,
  .w-datepicker > .w-input {
    align-items: stretch;
  }
  
</style>
